use hyprland::{
    data::Client,
    dispatch::{Dispatch, DispatchType, WindowIdentifier},
};
use serde::Deserialize;
use std::{env, fs, io::Write, path::PathBuf};

const DEFAULT_CONFIG_STR: &'static str = r#"
exec = "fuzzel"
args = ["-d", "--index"]

# 窗口标题长度显示
title_limit = 50
"#;

#[derive(Deserialize)]
pub struct Command {
    exec: String,
    args: Vec<String>,
    title_limit: Option<usize>,
}

fn home_dir() -> PathBuf {
    let home: String = env::var("HOME").expect("$HOME not found");
    PathBuf::from(home)
}

fn get_config_dir() -> PathBuf {
    let config_dir: PathBuf =
        std::env::var("XDG_CONFIG_HOME").map_or_else(|_| home_dir().join(".config"), PathBuf::from);
    config_dir.join("hypr")
}

impl Command {
    pub fn new() -> Self {
        let config_dir = get_config_dir();
        if !config_dir.exists() {
            fs::create_dir_all(&config_dir).expect("create config dir failed");
        }
        let config = config_dir.join("hyprwls.toml");
        if config.exists() {
            let buf = fs::read_to_string(config).expect("read config failed");
            toml::from_str(&buf).unwrap()
        } else {
            let mut config = fs::File::create(config).unwrap();
            config.write_all(DEFAULT_CONFIG_STR.as_bytes()).unwrap();
            toml::from_str(&DEFAULT_CONFIG_STR).unwrap()
        }
    }

    pub fn exist_and_kill(&self) -> bool {
        let status = std::process::Command::new("pkill")
            .arg(&self.exec)
            .status()
            .unwrap();
        status.success()
    }

    pub fn select_client(&self, mut clients: Vec<Client>) -> Option<Client> {
        clients.sort_by_key(|x| x.focus_history_id);
        if clients.len() > 1 {
            clients.swap(0, 1);
        }
        let mut child = std::process::Command::new(&self.exec)
            .args(&self.args)
            .stdin(std::process::Stdio::piped())
            .stdout(std::process::Stdio::piped())
            .spawn()
            .unwrap();
        let titles: Vec<String> = clients
            .iter()
            .map(|client| {
                let mut title = format!("{}", client.title);
                if title.contains(&client.initial_title) {
                    title = title.replace(&client.initial_title, "~");
                }
                title = format!("{} - {}", client.initial_title, title);
                if let Some(limit) = self.title_limit {
                    if title.chars().count() > limit {
                        title = title.chars().take(limit).collect();
                        title = format!("{}...", title);
                    }
                }
                let workspace = client.workspace.name.replace("special:", "");
                format!("{}. {}", workspace, title)
            })
            .collect();
        let mut stdin = child.stdin.take().expect("got stdin failed");
        std::thread::spawn(move || {
            stdin
                .write_all(titles.join("\n").as_bytes())
                .expect("write to stdin failed");
        });
        let output = child.wait_with_output().expect("wait with output failed");
        let output = String::from_utf8_lossy(&output.stdout);
        if output.is_empty() {
            return None;
        }

        let index: usize = output.trim().parse().expect("output is not index!");
        Some(clients.swap_remove(index))
    }

    pub fn fouce_client(&self, client: Client) {
        Dispatch::call(DispatchType::FocusWindow(WindowIdentifier::Address(
            client.address,
        )))
        .expect("focus window failed");
        if client.floating {
            Dispatch::call(DispatchType::BringActiveToTop).expect("active to top failed");
        }
    }
}
